﻿
// ================================================================================================
// -*- C++ -*-
// File: fnt.cpp
// Author: Guilherme R. Lampert
// Created on: 18/02/16
// Brief: FNT file parsing.
//
// This source code is released under the MIT license.
// See the accompanying LICENSE file for details.
// ================================================================================================

#include "fnt.hpp"
#include <cassert>
#include <codecvt>

// ========================================================
// Text FNT file processing:
// ========================================================
const FontChar* FontCharSet::GetChar(DWORD n) const
{
	auto iterFind = map_FontChar.find(n);
	if (map_FontChar.end() != iterFind)
	{
		return &iterFind->second;
	}
	return nullptr;
}

struct TextFntParser
{
    FILE* fntFile          = nullptr;
    FontChar* currentChar  = nullptr;
    bool prevTokenWasChar  = false;
    int lineNum            = 0;
	//int largestPage		   = 0;

    enum { LineCharsMax = 4096 };
    wchar_t lineBuffer[LineCharsMax];

    void closeFile()
    {
        if (fntFile != nullptr)
        {
            std::fclose(fntFile);
            fntFile = nullptr;
        }
    }

    // Ensures the file is always closed, even if an exception is thrown.
    ~TextFntParser() { closeFile(); }
};

class FontToolError final
	: public std::runtime_error
{
public:
	using std::runtime_error::runtime_error;
};

void error(const std::wstring & message)
{
	std::wstring_convert<std::codecvt_utf8<wchar_t> > myconv;
	throw FontToolError{ myconv.to_bytes(message) };
}

bool strStartsWith(const wchar_t* str, const wchar_t* prefix)
{
	const auto strLen = std::wcslen(str);
	const auto prefixLen = std::wcslen(prefix);

	if (strLen < prefixLen)
	{
		return false;
	}
	if (strLen == 0 || prefixLen == 0)
	{
		return false;
	}
	return std::wcsncmp(str, prefix, prefixLen) == 0;
}


int scanInt(TextFntParser & parser, const wchar_t* token)
{
    wchar_t* endPtr = nullptr;
    int result = static_cast<int>(std::wcstol(token, &endPtr, 0));

    if (endPtr == nullptr || endPtr == token)
    {
        error(L"FNT parse error at line " + std::to_wstring(parser.lineNum) +
              L": Expected number instead of token '" + std::wstring(token) + L"'!");
    }

    return result;
}

void handleFntToken(TextFntParser& parser, const wchar_t* token, FontCharSet& charSetOut)
{
    assert(token != nullptr);
    if (std::wcscmp(token, L"char") == 0)
    {
        parser.prevTokenWasChar = true;
        return;
    }

    if (strStartsWith(token, L"base="))
    {
        charSetOut.charBaseHeight = scanInt(parser, token + 5);
    }
    // Need prevTokenWasChar because there's "char id=" and "page id=".
    else if (strStartsWith(token, L"id=") && parser.prevTokenWasChar)
    {
        const int charIndex = scanInt(parser, token + 3);
        if (charIndex < 0)
        {
            error(L"FNT line " + std::to_wstring(parser.lineNum) + L": Char index out-of-range!");
        }
		FontChar stFontChar;
		auto iterInsert = charSetOut.map_FontChar.insert(std::make_pair(charIndex, stFontChar));
        parser.currentChar = &iterInsert.first->second;
        charSetOut.charCount++;
    }
    else if (strStartsWith(token, L"x="))
    {
        assert(parser.currentChar != nullptr);
        parser.currentChar->x = scanInt(parser, token + 2);
    }
    else if (strStartsWith(token, L"y="))
    {
        assert(parser.currentChar != nullptr);
        parser.currentChar->y = scanInt(parser, token + 2);
    }
	else if (strStartsWith(token, L"width="))
	{
		parser.currentChar->width = scanInt(parser, token + 6);
	}
    else if (strStartsWith(token, L"height="))
    {
		parser.currentChar->height = scanInt(parser, token + 7);
    }
	else if (strStartsWith(token, L"page="))
	{
		parser.currentChar->page = scanInt(parser, token + 5);
	}
    parser.prevTokenWasChar = false;
}

void parseTextFntFile(const std::wstring& filename, FontCharSet& charSetOut)
{
    TextFntParser parser;

	_wfopen_s(&parser.fntFile, filename.c_str(), L"rt");

    if (parser.fntFile == nullptr)
    {
        error(L"Unable to open FNT file \"" + filename + L"\" for reading!");
    }

    while (!std::feof(parser.fntFile))
    {
        wchar_t* line = std::fgetws(parser.lineBuffer, TextFntParser::LineCharsMax, parser.fntFile);
        parser.lineNum++;

        if (line == nullptr)
        {
            break;
        }

		wchar_t* next_token = nullptr;
        const wchar_t* token = wcstok_s(line, L" \t\n\r", &next_token); // Split each line by whitespace.
        while (token != nullptr)
        {
            handleFntToken(parser, token, charSetOut);
            token = wcstok_s(nullptr, L" \t\n\r", &next_token);
        }
    }

	charSetOut.TextureBitMap;

    // TextFntParser destructor closes the file.
}
